﻿// Copyright 2009 Mike Geise
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Falcon.Forums.Services
{
	using System;
	using System.Collections.Generic;
	using Falcon.Core.Collections;
	using Falcon.Core.Domain.Services;
	using Falcon.Core.Extensions;
	using Falcon.Forums.Domain;
	using Falcon.Forums.Domain.Enums;
	using Falcon.Forums.Domain.Repositories;
	using Falcon.Forums.Domain.Services;
	using Falcon.Forums.Domain.Values;

	public class ForumTopicService : IForumTopicService
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ForumTopicService"/> class.
		/// </summary>
		/// <param name="identityService">The identity service.</param>
		/// <param name="logService">The log service.</param>
		/// <param name="pollService">The poll service.</param>
		/// <param name="pollChoiceService">The poll choice service.</param>
		/// <param name="forumService">The forum service.</param>
		/// <param name="forumPostService">The forum post service.</param>
		/// <param name="forumPostAttachmentService">The forum post attachment service.</param>
		/// <param name="forumTopicSubscriptionService">The forum topic subscription service.</param>
		/// <param name="forumTopicTagService">The forum topic tag service.</param>
		/// <param name="forumTopicRepository">The forum topic repository.</param>
		public ForumTopicService(
			IIdentityService identityService,
			ILogService logService,
			IPollService pollService,
			IPollChoiceService pollChoiceService,
			IForumService forumService,
			IForumPostService forumPostService,
			IForumPostAttachmentService forumPostAttachmentService,
			IForumTopicSubscriptionService forumTopicSubscriptionService,
			IForumTopicTagService forumTopicTagService,
			IForumTopicRepository forumTopicRepository)
		{
			this.IdentityService = identityService;
			this.LogService = logService;
			this.PollService = pollService;
			this.PollChoiceService = pollChoiceService;
			this.ForumService = forumService;
			this.ForumPostService = forumPostService;
			this.ForumPostAttachmentService = forumPostAttachmentService;
			this.ForumTopicSubscriptionService = forumTopicSubscriptionService;
			this.ForumTopicTagService = forumTopicTagService;
			this.ForumTopicRepository = forumTopicRepository;
		}

		/// <summary>
		/// Gets or sets the identity service.
		/// </summary>
		/// <value>The identity service.</value>
		public IIdentityService IdentityService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the log service.
		/// </summary>
		/// <value>The log service.</value>
		public ILogService LogService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the poll service.
		/// </summary>
		/// <value>The poll service.</value>
		public IPollService PollService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the poll choice service.
		/// </summary>
		/// <value>The poll choice service.</value>
		public IPollChoiceService PollChoiceService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum service.
		/// </summary>
		/// <value>The forum service.</value>
		public IForumService ForumService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum post service.
		/// </summary>
		/// <value>The forum post service.</value>
		public IForumPostService ForumPostService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum post attachment service.
		/// </summary>
		/// <value>The forum post attachment service.</value>
		public IForumPostAttachmentService ForumPostAttachmentService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum topic subscription service.
		/// </summary>
		/// <value>The forum topic subscription service.</value>
		public IForumTopicSubscriptionService ForumTopicSubscriptionService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum topic tag service.
		/// </summary>
		/// <value>The forum topic tag service.</value>
		public IForumTopicTagService ForumTopicTagService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum topic repository.
		/// </summary>
		/// <value>The forum topic repository.</value>
		public IForumTopicRepository ForumTopicRepository
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets the forum topic by the specified specification.
		/// </summary>
		/// <param name="specification">The specification.</param>
		/// <returns></returns>
		public ForumTopic GetBy(ForumTopicSpecification specification)
		{
			var topic = this.ForumTopicRepository.GetBy(specification);

			if (topic != null)
			{
				if (topic.PollId > 0)
				{
					topic.Poll = this.PollService.GetById(topic.PollId);
				}

				topic.Tags = this.ForumTopicTagService.GetAll(new ForumTopicTagSpecification
				{
					TopicId = topic.Id
				});
			}

			return topic;
		}

		/// <summary>
		/// Gets the forum topic by id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <returns></returns>
		public ForumTopic GetById(int id)
		{
			var topic = this.ForumTopicRepository.GetById(id);

			if (topic != null)
			{
				if (topic.PollId > 0)
				{
					topic.Poll = this.PollService.GetById(topic.PollId);
				}

				topic.Tags = this.ForumTopicTagService.GetAll(new ForumTopicTagSpecification
				{
					TopicId = topic.Id
				});

				if ((topic.IsPending || topic.IsDeleted) && !IdentityService.IsPrivileged)
				{
					return null;
				}
			}

			return topic;
		}

		/// <summary>
		/// Gets all the forum topics by the specified specification.
		/// </summary>
		/// <param name="specification">The specification.</param>
		/// <returns></returns>
		public IPagedList<ForumTopic, ForumTopicSpecification> GetPaged(ForumTopicSpecification specification)
		{
			if (IdentityService.IsPrivileged)
			{
				specification.Status = new[]
				{
					ForumTopicStatus.Published,
					ForumTopicStatus.Pending,
					ForumTopicStatus.Closed,
					ForumTopicStatus.Deleted
				};
			}
			else
			{
				specification.Status = new[]
				{
					ForumTopicStatus.Published,
					ForumTopicStatus.Closed
				};
			}

			var stickies = this.ForumTopicRepository.GetPaged(new ForumTopicSpecification
			{
				ForumId = specification.ForumId,
				Scope = ForumTopicScope.Sticky,
				Status = specification.Status,
				Sort = specification.Sort,
				Order = specification.Order,
				Page = 1,
				Limit = int.MaxValue
			});

			var topics = this.ForumTopicRepository.GetPaged(specification);

			stickies.ForEach(x => topics.Items.Insert(0, x));

			return topics;
		}

		/// <summary>
		/// Gets the count of topics by the specified specification.
		/// </summary>
		/// <param name="specification">The specification.</param>
		/// <returns></returns>
		public int Count(ForumTopicSpecification specification)
		{
			return this.ForumTopicRepository.Count(specification);
		}

		/// <summary>
		/// Creates the forum topic.
		/// </summary>
		/// <returns></returns>
		public ForumTopic Create()
		{
			var topic = new ForumTopic
			{
				Replies = 0,
				Views = 0,
				Created = DateTime.Now
			};

			topic.Updated = topic.Created;

			return topic;
		}

		/// <summary>
		/// Inserts the forum topic into the storage.
		/// </summary>
		/// <param name="topic">The <see cref="ForumTopic"/>.</param>
		public void Insert(ForumTopic topic)
		{
			topic.Validate();

			this.ForumTopicRepository.Insert(topic);

			var forum = this.ForumService.GetById(topic.ForumId);

			forum.Total.UpdateTopics();

			this.ForumService.Update(forum);
		}

		/// <summary>
		/// Inserts the forum topic into the storage.
		/// </summary>
		/// <param name="topic">The <see cref="ForumTopic"/>.</param>
		/// <param name="value">The <see cref="ForumTopicCreate"/>.</param>
		public void Insert(ForumTopic topic, ForumTopicCreate value)
		{
			value.Validate();

			var identity = IdentityService.Current;

			ProcessPoll(topic, value);

			topic.ForumId = value.ForumId;
			topic.Slug = value.Title.Slug();

			this.Insert(topic);

			var post = this.ForumPostService.Create();

			post.ForumId = topic.ForumId;
			post.TopicId = topic.Id;
			post.UserId = identity.Id;
			post.Title = value.Title;
			post.Body = value.Body;

			this.ForumPostService.Insert(post);
			this.ForumPostAttachmentService.Insert(post, value.Attachments);

			topic.PostId = post.Id;

			this.Update(topic);

			if (value.Tags.HasValue())
			{
				var tags = value.Tags.Split(',');

				for (var i = 0; i < tags.Length; i++)
				{
					if (!tags[i].HasValue())
					{
						continue;
					}

					var tag = this.ForumTopicTagService.Create();

					tag.TopicId = topic.Id;
					tag.Slug = tags[i].Slug();

					this.ForumTopicTagService.Insert(tag);
				}
			}

			if (value.IsNotSubscribed)
			{
				return;
			}

			var subscription = this.ForumTopicSubscriptionService.Create();

			subscription.TopicId = topic.Id;
			subscription.UserId = identity.Id;
			subscription.Notification = value.Notification;

			this.ForumTopicSubscriptionService.Insert(subscription);
		}

		/// <summary>
		/// Updates the specified forum topic in the storage..
		/// </summary>
		/// <param name="topic">The <see cref="ForumTopic"/>.</param>
		public void Update(ForumTopic topic)
		{
			topic.Validate();

			topic.Updated = DateTime.Now;

			this.ForumTopicRepository.Update(topic);
		}

		/// <summary>
		/// Updates the specified topics.
		/// </summary>
		/// <param name="topics">The topics.</param>
		/// <param name="action">The action.</param>
		public void Update(IList<ForumTopic> topics, ForumTopicAction action)
		{
			switch (action)
			{
				case ForumTopicAction.UnDelete:
					{
						this.Update(topics, ForumTopicStatus.Published);
					}
					break;
				case ForumTopicAction.Open:
					{
						this.Update(topics, ForumTopicStatus.Published);
					}
					break;
				case ForumTopicAction.Close:
					{
						this.Update(topics, ForumTopicStatus.Closed);
					}
					break;
				case ForumTopicAction.Stick:
					{
						this.Update(topics, ForumTopicScope.Sticky);
					}
					break;
				case ForumTopicAction.UnStick:
					{
						this.Update(topics, ForumTopicScope.Normal);
					}
					break;
			}
		}

		/// <summary>
		/// Deletes the specified forum topic in the storage.
		/// </summary>
		/// <param name="topic">The <see cref="ForumTopic"/>.</param>
		public void Delete(ForumTopic topic)
		{
			this.ForumTopicRepository.Delete(topic);
		}

		/// <summary>
		/// Deletes the specified topics.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Delete(ForumTopicDelete value)
		{
			this.Delete(value.TopicsToDelete, value.Reason);
		}

		/// <summary>
		/// Deletes the specified topics.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Delete(ForumTopicDeleteSpam value)
		{
			var deleted = this.Delete(value.TopicsToDelete, value.Reason);

			foreach (var topic in deleted)
			{
				if (value.Ban)
				{
					// ban user
				}

				if (!value.Purge)
				{
					continue;
				}

				var posts = this.ForumPostService.GetPaged(new ForumPostSpecification
				{
					UserId = topic.Post.User.Id
				});

				this.ForumPostService.Delete(posts.Items, value.Reason);
			}
		}

		/// <summary>
		/// Moves the topics specified in <see cref="ForumTopicMove"/>.
		/// </summary>
		/// <param name="value">The <see cref="ForumTopicMove"/>.</param>
		public void Move(ForumTopicMove value)
		{
			var forum = this.ForumService.GetById(value.ForumId);

			if (forum == null || value.TopicsToMove == null)
			{
				return;
			}

			for (var i = 0; i < value.TopicsToMove.Length; i++)
			{
				var topic = this.GetById(value.TopicsToMove[i].Id);

				if (topic == null)
				{
					continue;
				}

				var log = this.LogService.Log(Logs.MovedTopicToForum.Fmt(topic.Id, forum.Id, topic.ForumId));

				topic.ForumId = forum.Id;
				topic.LogId = log.Id;

				this.ForumTopicRepository.Update(topic);

				var posts = this.ForumPostService.GetPaged(new ForumPostSpecification
				{
					TopicId = topic.Id
				});

				foreach (var post in posts)
				{
					post.ForumId = forum.Id;
					post.LogId = log.Id;

					this.ForumPostService.Update(post);
				}
			}
		}

		/// <summary>
		/// Merges the topics specified in <see cref="ForumTopicMerge"/>.
		/// </summary>
		/// <param name="value">The <see cref="ForumTopicMerge"/>.</param>
		public void Merge(ForumTopicMerge value)
		{
			var forum = this.ForumService.GetById(value.ForumId);
			var values = value.TopicsToMerge;

			if (forum == null || values == null)
			{
				return;
			}

			for (var i = 0; i < values.Length; i++)
			{
				var topic = this.GetById(values[i].Id);

				if (topic == null)
				{
					continue;
				}

				if (topic.Id.Equals(value.TopicId))
				{
					var log = this.LogService.Log(Logs.MergedTopicsToTopic.Fmt(topic.Id, forum.Id));

					topic.ForumId = forum.Id;
					topic.LogId = log.Id;

					this.ForumTopicRepository.Update(topic);
				}
				else
				{
					var log = this.LogService.Log(Logs.MergedTopicToTopic.Fmt(topic.Id, value.TopicId, forum.Id));

					var posts = this.ForumPostService.GetPaged(new ForumPostSpecification
					{
						TopicId = topic.Id
					});

					foreach (var post in posts)
					{
						post.ForumId = forum.Id;
						post.TopicId = value.TopicId;
						post.LogId = log.Id;

						this.ForumPostService.Update(post);
					}

					this.Delete(topic);
				}
			}
		}

		/// <summary>
		/// Transforms the specified values that are selected to the topics list.
		/// </summary>
		/// <param name="values">The values.</param>
		/// <param name="topics">The topics.</param>
		public void Transform(ForumTopicUpdateValue[] values, IList<ForumTopic> topics)
		{
			if (values == null)
			{
				return;
			}

			for (var i = 0; i < values.Length; i++)
			{
				if (!values[i].Selected)
				{
					continue;
				}

				var topic = this.GetById(values[i].Id);

				if (topic == null)
				{
					continue;
				}

				topics.Add(topic);
			}
		}

		/// <summary>
		/// Marks the topic as updated with the specified forum post.
		/// </summary>
		/// <param name="post">The post.</param>
		public void MarkUpdated(ForumPost post)
		{
			if (post == null)
			{
				return;
			}

			// get the topic
			var topic = ForumTopicRepository.GetById(post.TopicId);

			// set updated values
			topic.Updated = DateTime.Now;
			topic.LastPost = post;
			topic.UpdateReplies();

			// update topic in repository
			ForumTopicRepository.Update(topic);

			// notify subscribers
			ForumTopicSubscriptionService.NotifySubscribers(new ForumTopicSubscriptionSpecification
			{
				TopicId = topic.Id
			});
		}

		/// <summary>
		/// Updates the specified topics.
		/// </summary>
		/// <param name="topics">The topics.</param>
		/// <param name="status">The status.</param>
		private void Update(IEnumerable<ForumTopic> topics, ForumTopicStatus status)
		{
			foreach (var topic in topics)
			{
				Update(topic, status, topic.Scope);
			}
		}

		/// <summary>
		/// Updates the specified topics.
		/// </summary>
		/// <param name="topics">The topics.</param>
		/// <param name="scope">The scope.</param>
		private void Update(IEnumerable<ForumTopic> topics, ForumTopicScope scope)
		{
			foreach (var topic in topics)
			{
				Update(topic, topic.Status, scope);
			}
		}

		/// <summary>
		/// Updates the specified topic.
		/// </summary>
		/// <param name="topic">The topic.</param>
		/// <param name="status">The status.</param>
		/// <param name="scope">The scope.</param>
		private void Update(ForumTopic topic, ForumTopicStatus status, ForumTopicScope scope)
		{
			var log = this.LogService.Log(Logs.UpdatedTopicStatus.Fmt(topic.Id, status, scope));

			topic.LogId = log.Id;
			topic.Status = status;
			topic.Scope = scope;

			this.ForumTopicRepository.Update(topic);
		}

		/// <summary>
		/// Deletes the specified values.
		/// </summary>
		/// <param name="values">The values.</param>
		/// <param name="reason">The reason.</param>
		/// <returns></returns>
		private IList<ForumTopic> Delete(ForumTopicUpdateValue[] values, string reason)
		{
			var deleted = new List<ForumTopic>();
			var identity = this.IdentityService.Current;

			if (values == null)
			{
				return deleted;
			}

			for (var i = 0; i < values.Length; i++)
			{
				var topic = this.ForumTopicRepository.GetById(values[i].Id);

				if (topic == null || topic.IsDeleted)
				{
					continue;
				}

				var log = this.LogService.Log(Logs.UpdatedTopicStatusToDeleted.Fmt(topic.Id), reason);

				topic.LogId = log.Id;
				topic.Status = ForumTopicStatus.Deleted;

				this.ForumTopicRepository.Update(topic);

				deleted.Add(topic);
			}

			return deleted;
		}

		/// <summary>
		/// Processes the poll for the specified topic.
		/// </summary>
		/// <param name="topic">The topic.</param>
		/// <param name="value">The value.</param>
		private void ProcessPoll(ForumTopic topic, ForumTopicCreate value)
		{
			if (!value.HasPoll)
			{
				return;
			}

			var poll = this.PollService.Create();

			poll.Title = value.Question;

			this.PollService.Insert(poll);

			for (var i = 0; i < value.Choices.Length; i++)
			{
				var choice = PollChoiceService.Create();

				choice.PollId = poll.Id;
				choice.Title = value.Choices[i];
				choice.Order = i;

				this.PollChoiceService.Insert(choice);
			}

			topic.PollId = poll.Id;
		}
	}
}